const express = require('express')
const bodyParser = require('body-parser')
const path = require('path')
const fse = require('fs-extra')
const fs = require('fs')
const multiparty = require('multiparty')
const app = express()
// 设置静态文件目录
app.use(express.static(__dirname + '/public'))
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())

// 设置跨域访问
app.use(function (req, res, next) {
    // 设置允许跨域的域名，* 表示允许任意域名跨域
    res.header('Access-Control-Allow-Origin', req.headers.origin)
    // 允许携带cookie
    res.header('Access-Control-Allow-Credentials', 'true')
    // 允许的header类型
    res.header('Access-Control-Allow-Headers', ['content-type', 'Origin'])
    // 跨域允许的请求方式
    res.header('Access-Controll-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')

    res.header('Access-Controll-Allow-Max-Age', `${20}`)
    if (req.method.toLowerCase() === 'options') res.send(200)
    // 让options尝试请求快速结束
    else next()
})

// 上传的切片文件的存储路径,这里使用相对路径，可以定义上传的切片文件存储到其他路径
const ALL_CHUNKS_PATH = path.resolve(__dirname, 'chunks')
// 切片合并完成后的文件存储路径,这里使用相对路径，可以定义切片合并完成后文件存储到其他路径
const UPLOAD_FILE_PATH = path.resolve(__dirname, 'public/files')

/**
 * 文件上传接口
 *
 */
app.post('/upload', async (req, res) => {
    const multipartyForm = new multiparty.Form()
    multipartyForm.parse(req, async (err, fields, files) => {
        if (err) {
            res.send({
                code: 0,
                message: '文件切片上传失败',
            })
            return
        }
        // 前端发送的切片文件信息
        const [file] = files.file
        // 前端发送的完整文件名 fileName 和分片名 chunkName
        const {
            fileName: [fileName],
            chunkName: [chunkName],
        } = fields
        // 当前文件的切片存储路径（将文件名作为切片的目录名）
        const chunksPath = path.resolve(ALL_CHUNKS_PATH, fileName)
        
        // 判断当前文件的切片目录是否存在
        if (!fse.existsSync(chunksPath)) {
            // 创建切片目录
            fse.mkdirSync(chunksPath)
        }
        // 将前端发送的切片文件移动到切片目录中
        await fse.move(file.path, `${chunksPath}/${chunkName}`)
        res.send({
            code: 1,
            message: '切片上传成功',
        })
    })
})

/**
 * 合并切片接口
 *
 */
app.post('/merge', async (req, res) => {
    // 获取前端发送的参数
    const { chunkSize, fileName } = req.body
    // 当前文件切片合并成功后的文件存储路径
    const uploadedFile = path.resolve(UPLOAD_FILE_PATH, fileName)
    // 找到当前文件所有切片的存储目录路径
    const chunksPath = path.resolve(ALL_CHUNKS_PATH, fileName)
    // 读取所有的切片文件，获取到文件名
    const chunksName = await fse.readdir(chunksPath)
    // 对切片文件名按照数字大小排序
    chunksName.sort((a, b) => a - 0 - (b - 0))
    // 合并切片
    const unlinkResult = chunksName.map((name, index) => {
        // 获取每一个切片路径
        const chunkPath = path.resolve(chunksPath, name)
        // 获取要读取切片文件内容
        const readChunk = fse.createReadStream(chunkPath)
        // 获取要写入切片文件配置
        const writeChunk = fse.createWriteStream(uploadedFile, {
            start: index * chunkSize,
            end: (index + 1) * chunkSize,
        })
        // 将读取到的 readChunk 内容写入到 writeChunk 对应位置
        readChunk.pipe(writeChunk)

        return new Promise(resolve => {
            // 文件读取结束后删除切片文件(必须要将文件全部删除后，才能外层文件夹)
            readChunk.on('end', () => {
                fse.unlinkSync(chunkPath)
                resolve()
            })
        })
    })
    // 等到所有切片文件合并完成，且每一个切片文件都删除成功
    await Promise.all(unlinkResult)

    // 读取所有的切片文件，获取到文件名
    const chunksNameTemp = await fse.readdir(chunksPath)

    if (chunksNameTemp.length === 0) {
        // 删除切片文件所在目录
        fse.rmdirSync(chunksPath)
        res.send({
            code: 1,
            message: '文件上传成功',
        })
    }
})

/**
 * 校验文件接口
 */
app.post('/verify', async (req, res) => {
    // 获取前端发送的文件名
    const { fileName } = req.body
    // 获取当前文件路径（如果上传成功过的保存路径）
    const filePath = path.resolve(UPLOAD_FILE_PATH, fileName)
    // 判断文件是否存在
    if (fse.existsSync(filePath)) {
        res.send({
            code: 1,
            message: '文件已存在，不需要重新上传',
        })
        return
    }
    // 断点续传：判断文件是否有上传的一部分切片内容
    // 获取该文件的切片文件的存储目录
    const chunksPath = path.resolve(ALL_CHUNKS_PATH, fileName)
  
    // 判断该目录是否存在
    if (fse.existsSync(chunksPath)) {
        // 目录存在，则说明文件之前有上传过一部分，但是没有完整上传成功
        // 读取之前已上传的所有切片文件名
        const uploaded = await fse.readdir(chunksPath)
        res.send({
            code: 0,
            message: '该文件有部分上传数据',
            uploaded,
        })
        return
    }

    res.send({
        code: 0,
        message: '文件未上传过',
        uploaded:[]
    })
})


// 后续的接口配置代码
app.listen(3000, () => console.log('3000 端口启动成功'))
